//
//  Card.m
//  PrefCore
//
//  Created by Sergey on 1/8/14.
//  Copyright (c) 2014 Play-on-smart. All rights reserved.
//

#import "PSCard.h"

// Suit implementation

PSCardSuit PSCardSuitRising[PS_CARD_SUIT_RISING_LEN] = {
    kSuitSpades
,   kSuitClubs
,   kSuitDiamonds
,   kSuitHearts
};

static PSCardIndex shifts[] = {24, 16, 8, 0};

PSCardSuit PSCardSuitNext(PSCardSuit suit) {
    assert(suit < kSuitNone);
    return (PSCardSuit)(suit+1);
}

PSCardSuit PSCardSuitPrevious(PSCardSuit suit) {
    assert(suit > kSuitSpades);
    return (PSCardSuit)(suit-1);
}

PSCardIndex PSCardSuitPower(PSCardSuit suit) {
    assert(suit >= kSuitSpades && suit <= kSuitHearts);
    return shifts[suit];
}

NSString* PSCardSuitDescription(PSCardSuit suit) {
    assert(suit >= kSuitSpades && suit <= kSuitNone);
    static NSString* symbols[5];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        symbols[kSuitSpades]   = @"\u2660";
        symbols[kSuitClubs]    = @"\u2663";
        symbols[kSuitDiamonds] = @"\u2666";
        symbols[kSuitHearts]   = @"\u2665";
        symbols[kSuitNone]     = @".";
    });
    return symbols[suit];
}

NSString* PSCardSuitShortName(PSCardSuit suit) {
    assert(suit >= kSuitSpades && suit <= kSuitNone);
    static NSString* shortNames[5];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shortNames[kSuitSpades]   = @"s";
        shortNames[kSuitClubs]    = @"c";
        shortNames[kSuitDiamonds] = @"d";
        shortNames[kSuitHearts]   = @"h";
        shortNames[kSuitNone]     = @".";
    });
    return shortNames[suit];
}

// Rank implementation

PSCardRank PSCardRankRising[PS_CARD_RANK_COUNT] = {
    kRankSeven
,   kRankEight
,   kRankNine
,   kRankTen
,   kRankJack
,   kRankQueen
,   kRankKing
,   kRankAce
};

PSCardRank PSCardRankFalling[PS_CARD_RANK_COUNT] = {
    kRankAce
,   kRankKing
,   kRankQueen
,   kRankJack
,   kRankTen
,   kRankNine
,   kRankEight
,   kRankSeven
};

static PSCardRank PSCardRankForHash(PSCardRankSet set) {
    assert(set <= 128);
    static PSCardRank h2r[129];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        for (int i = 0 ; i != 129 ; i++) {
            h2r[i] = kRankNull;
        }
        for (int i = 0 ; i != PS_CARD_RANK_COUNT ; i++) {
            PSCardRank rank = PSCardRankRising[i];
            h2r[PSCardRankHash(rank)] = rank;
        }
    });
    return h2r[set];
}

NSString *PSCardRankDescription(PSCardRank rank) {
    assert(rank >= kRankSeven && rank <= kRankAce);
    static NSString* titles[8];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        titles[kRankSeven] = @"7";
        titles[kRankEight] = @"8";
        titles[kRankNine]  = @"9";
        titles[kRankTen]   = @"10";
        titles[kRankJack]  = @"J";
        titles[kRankQueen] = @"Q";
        titles[kRankKing]  = @"K";
        titles[kRankAce]   = @"A";
    });
    return titles[rank];
}

extern inline PSCardIndex PSCardSetSize(PSCardRankSet hash);

extern inline BOOL PSCardSetContainsRank(PSCardRankSet set, PSCardRank rank);

extern inline BOOL PSCardSetContainsCard(PSCardRankSet set, PSCardRankHashType card);

extern inline PSCardRankSet PSCardSetCombine(PSCardRankSet one, PSCardRankSet two);

extern inline PSCardRankSet PSCardSetRemoveRank(PSCardRankSet set, PSCardRank rank);

extern inline PSCardRankSet PSCardSetRemoveCard(PSCardRankSet set, PSCardRankHashType card);

PSCardRank PSCardSetHighestRank(PSCardRankSet set) {
    // The algorithm is from here:
    // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
    if (!set) {
        return kRankNull;
    }
    PSCardRank r;
    PSCardIndex shift = (set > 0x0F) << 2;
    set >>= shift;
    r = shift;
    shift = (set > 0x03) << 1;
    set >>= shift;
    return r | shift | (set >> 1);
}

PSCardRank PSCardSetLowestRank(PSCardRankSet set) {
    // The algorithm is from here:
    // http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightParallel
    PSCardIndex c = 8;
    set &= -((int8_t)set);
    if (set) c--;
    if (set & 0x0F) c -= 4;
    if (set & 0x33) c -= 2;
    if (set & 0x55) c -= 1;
    return c != 8 ? (PSCardRank)c : kRankNull;
}

PSCardRank PSCardSetNextHigherRank(PSCardRankSet set, PSCardRank rank) {
    for (int i = rank+1 ; i < PS_CARD_RANK_COUNT ; i++) {
        PSCardRank rank = PSCardRankRising[i];
        if (PSCardSetContainsRank(set, rank)) {
            return rank;
        }
    }
    return kRankNull;
}

PSCardRank PSCardSetNextLowerRank(PSCardRankSet set, PSCardRank rank) {
    for (int i = rank-1 ; i >= 0 ; i--) {
        PSCardRank rank = PSCardRankRising[i];
        if (PSCardSetContainsRank(set, rank)) {
            return rank;
        }
    }
    return kRankNull;
}

void PSCardSetDescription(NSMutableString *buf, PSCardRankSet set) {
    BOOL first = YES;
    for (int i = 0 ; i != PS_CARD_RANK_COUNT ; i++) {
        PSCardRank rank = PSCardRankFalling[i];
        if (!PSCardSetContainsRank(set, rank)) {
            continue;
        }
        if (!first) {
            [buf appendString:@", "];
        } else {
            first = NO;
        }
        [buf appendString:PSCardRankDescription(rank)];
    }
}

extern inline BOOL PSCardIsValidIndex(PSCardIndex compressed);

@interface PSCard ()

-(instancetype)initWithSuit:(PSCardSuit)suit rank:(PSCardRank)rank andIndex:(PSCardIndex)index;

-(NSUInteger)hash;

-(BOOL)isEqual:(id)object;

-(NSString*)description;

@end

@interface PSCardData : NSObject {
    @public
    PSCard* cardObjects[PS_CARD_SUIT_RISING_LEN*PS_CARD_RANK_COUNT+1];
    PSCard* cardTable[PS_CARD_SUIT_RISING_LEN][PS_CARD_RANK_COUNT];
}

-(instancetype)init;

@end

static PSCardData *cardData() {
    static PSCardData* instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[PSCardData alloc] init];
    });
    return instance;
}

@implementation PSCardData

-(instancetype)init {
    if (self = [super init]) {
        PSCardIndex i = 1;
        for (int s = 0 ; s != PS_CARD_SUIT_RISING_LEN ; s++) {
            PSCardSuit suit = PSCardSuitRising[s];
            for (int r = 0 ; r != PS_CARD_RANK_COUNT ; r++) {
                PSCardRank rank = PSCardRankRising[r];
                cardTable[suit][rank] =
                cardObjects[i] =
                    [[PSCard alloc] initWithSuit:suit rank:rank andIndex:i];
                i++;
            }
        }
        // 0 is invalid index value
        cardObjects[0] = nil;
    }
    return self;
}

@end

static PSCard* hash2card(uint16_t hash, PSCardSuit lower, PSCardSuit higher) {
    PSCardRankSet low8 = hash & 0xFF;
    PSCardRankSet high8 = (hash >> 8);
    assert(high8 || low8);
    return low8
         ? [PSCard cardWithSuit:lower andRank:PSCardRankForHash(low8)]
         : [PSCard cardWithSuit:higher andRank:PSCardRankForHash(high8)];
}

@implementation PSCard

-(instancetype)initWithSuit:(PSCardSuit)suit rank:(PSCardRank)rank andIndex:(PSCardIndex)index {
    if (self = [super init]) {
        _suit = suit;
        _rank = rank;
        _hash32 = PSCardRankHash(rank) << PSCardSuitPower(suit);
        _compressed = index;
    }
    return self;
}

+(instancetype) cardWithSuit:(PSCardSuit) suit andRank:(PSCardRank) rank {
    return cardData()->cardTable[suit][rank];
}

+(instancetype) cardWithIndex:(PSCardIndex)index {
    NSAssert(index <= PS_CARD_SUIT_RISING_LEN*PS_CARD_RANK_COUNT, @"Card index is invalid");
    return cardData()->cardObjects[index];
}

+(instancetype) cardWithHash:(PSCardHash) hash {
    uint16_t low16 = hash & 0xFFFF;
    uint16_t high16 = (hash >> 16);
    NSAssert(high16 || low16, @"Either high or low 16 bits of the hash must be non-zero.");
    return low16
         ? hash2card(low16, kSuitHearts, kSuitDiamonds)
         : hash2card(high16, kSuitClubs, kSuitSpades);
}

+(PSCardSuit) suitOfCardWithIndex:(PSCardIndex)index {
    return cardData()->cardObjects[index].suit;
}

+(PSCardRank) rankOfCardWithIndex:(PSCardIndex)index {
    return cardData()->cardObjects[index].rank;
}

-(NSUInteger)hash {
    return _hash32;
}

-(BOOL)isEqual:(id)object {
    return self == object; // Each card is a singleton
}

-(NSString*)description {
    return [NSString
        stringWithFormat:@"%@%@"
    ,   PSCardSuitDescription(_suit)
    ,   PSCardRankDescription(_rank)
    ];
}

@end
